package com.baizhi.repository.impl;

import com.baizhi.entity.Book;
import com.baizhi.repository.CustomBookRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//@Repository 注解将下面的实现类交给框架管理  类似Dao接口的实现类  不过这个实现类是自己完成实现代码的
//@Repository 注解其实是对操作数据库的类进行扫描的   以前的dao实现类被mapper文件代替了
@Repository
public class CustomBookRepositoryImpl implements CustomBookRepository {

    //1、这个实现类需要注入一个属性   本质是个工具类 专门用来操作ES数据库的
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    //2、下面重写的方法就是我们自定义的高亮查询的方法
    @Override
    public Map selectByNameForHighlight(int page, int limit, String name) {
        //思路：这个方法的核心内容是用上面注入的属性，调用其内的一个方法实现的
        //      方法是：queryForPage(参数1，参数2，参数3)  所以需要准备好三个参数
        //      queryForPage返回的是一个AggregatedPage<Book>类型的数据  可以通过getContent获得其中的具体数据
        //一、准备参数1：即创建这个查询的查询条件对象
        NativeSearchQuery build =
                new NativeSearchQueryBuilder()
                        .withQuery(QueryBuilders.termQuery("name",name))//这一行是关键词查询 类似原生java操作es中的setQuery方法
                        .withPageable(PageRequest.of(page-1,limit))//这一行是分页查询  类似原生java操作ES代码中的setFrom和setSize
                        .withHighlightFields(new HighlightBuilder.Field("name"))//这一行是设置那个字段进行高亮处理  类似原来的HighlightFields
                        .build();//这个是创建   根据上面的几个条件组合创建一个“查询条件”对象
        //二、参数2：指明要操作的是具体那个类的类对象
        //三、参数3：是对上面查询结果进行处理的方法  这里是对一个接口SearchResultMapper的回调应用
        AggregatedPage<Book> books = elasticsearchTemplate.queryForPage(build, Book.class, new SearchResultMapper() {
            //下面是接口SearchResultMapper回调内的方法  是用来处理查询结果的
            //其中方法的第一个参数就是查询的结果
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                //A、获取hits
                SearchHits hits = searchResponse.getHits();
                //B、准备list集合  用来封装数据
                List list = new ArrayList();
                ObjectMapper mapper = new ObjectMapper();//准备利用框架自带Jackson技术进行反序列化操作
                //C、遍历hits  对普通数据和高亮数据进行封装处理
                try {
                    for (SearchHit hit : hits) {
                        //首先，是对普通数据进行处理
                        String sourceAsString = hit.getSourceAsString();
                        //这个获得查询数据的string类型 而不是map类型  是为了通过下面的反序列化操作直接获取Book对象
                        Book book = mapper.readValue(sourceAsString, Book.class);
//                      book.setId(hit.getId());//注：这里若是用了UUID自动生成ID  id是string类型 可以这样获取_id的值赋值给Book对象中的id属性
                        //第二，是对高亮数据进行处理
                        Map<String, HighlightField> highlightFields = hit.getHighlightFields();//获取高亮数据所在的map
                        HighlightField name1 = highlightFields.get("name");//根据高亮字段获取高亮的数据数组
                        if(name1!=null){//判断  当这个数据数组不为null时  取出我们真正想要的结果
                            String name2 = name1.fragments()[0].toString();
                            //然后将真正的高亮数据封装到book对象中
                            book.setName(name2);
                        }
                        //最后  把封装好的每一条book对象一一添加到list集合中
                        list.add(book);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //D、将上面的list集合封装到下面的返回结果中
                //注意：该回调方法返回的是AggregatedPage接口的实现类对象AggregatedPageImpl
                //注意2：该回调方法的返回值  就是queryForPage方法的返回值books
                //注意3：该返回结果的里面封装了3个参数，分别是list数据、pageable分页、以及数据的总条数
                return new AggregatedPageImpl<>(list,pageable,hits.totalHits);
            }
        });
        //四、封装数据用来作为这个高亮查询方法的返回值
        Map map = new HashMap();
        map.put("data",books.getContent());//这个就是高亮查询的数据
        map.put("count",books.getTotalElements());//这个就是高亮查询的数据的总条数
        return map;
    }
}
